package com.jian.mygame;

import android.content.Context;
import android.graphics.Canvas;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.TextView;

class GameView extends SurfaceView implements SurfaceHolder.Callback{
	

    class GameThread extends Thread {
        /*
         * Difficulty setting constants
         */
        public static final int DIFFICULTY_EASY = 0;
        public static final int DIFFICULTY_HARD = 1;
        public static final int DIFFICULTY_MEDIUM = 2;
 
        /*
         * State-tracking constants
         */
        public static final int STATE_LOSE = 1;
        public static final int STATE_PAUSE = 2;
        public static final int STATE_READY = 3;
        public static final int STATE_RUNNING = 4;
        public static final int STATE_WIN = 5;

        /*
         * Goal condition constants
         */
        public static final int TARGET_ANGLE = 18; // > this angle means crash
        public static final int TARGET_BOTTOM_PADDING = 17; // px below gear
        public static final int TARGET_PAD_HEIGHT = 8; // how high above ground
        public static final int TARGET_SPEED = 28; // > this speed means crash
        public static final double TARGET_WIDTH = 1.6; // width of target

        /*
         * UI constants (i.e. the speed & fuel bars)
         */
        public static final int UI_BAR = 100; // width of the bar(s)
        public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)
                
        private boolean isStarted = false;
        
        /**
         * Current height of the surface/canvas.
         * 
         * @see #setSurfaceSize
         */
        private int mCanvasHeight = 1;

        /**
         * Current width of the surface/canvas.
         * 
         * @see #setSurfaceSize
         */
        private int mCanvasWidth = 1;

        /** Message handler used by thread to interact with TextView */
        private Handler mHandler;

        /** Handle to the surface manager object we interact with */
        private SurfaceHolder mSurfaceHolder;
     
        /** Indicate whether the surface has been created & is ready to draw */
        private boolean mRun = false;
        
        private GameHandler mGameHandler;
               
        public GameThread(SurfaceHolder surfaceHolder, Context context,
                Handler handler) {
            // get handles to some important objects
            mSurfaceHolder = surfaceHolder;
            mHandler = handler;
            mContext = context;
            
            mGameHandler = new GameHandler(context);
        }
        
        public GameThread(SurfaceHolder surfaceHolder, Context context,
                Handler handler, GameHandler gameHandler ) {
            // get handles to some important objects
            mSurfaceHolder = surfaceHolder;
            mHandler = handler;
            mContext = context;
            
            mGameHandler = gameHandler;
            mGameHandler.updateLastTime();
        }
        
        /**
         * Starts the game, setting parameters for the current difficulty.
         */
        public void doStart() {
            synchronized (mSurfaceHolder) {
            }
        }

        /**
         * Pauses the physics update & animation.
         */
        public void pause() {
        	super.suspend();
            synchronized (mSurfaceHolder) {
            	setRunning(false);
            }
        }

        /**
         * Restores game state from the indicated Bundle. Typically called when
         * the Activity is being restored after having been previously
         * destroyed.
         * 
         * @param savedState Bundle containing the game state
         */
        public synchronized void restoreState(Bundle savedState) {
            synchronized (mSurfaceHolder) {
            		
            }
        }


        @Override
        public void run() {
            while (mRun) {
                Canvas c = null;
                //mAnimation.updateTimer();
                
                try {
                    c = mSurfaceHolder.lockCanvas(null);
                    synchronized (mSurfaceHolder) {
                    	updateGame(c);
                    }
                } finally {
                    // do this in a finally so that if an exception is thrown
                    // during the above, we don't leave the Surface in an
                    // inconsistent state
                    if (c != null) {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }

        /**
         * Draws the ship, fuel/speed bars, and background to the provided
         * Canvas.
         */
        private void updateGame(Canvas canvas) {
			mGameHandler.updateGame(canvas);
        }
        
        /**
         * Dump game state to the provided Bundle. Typically called when the
         * Activity is being suspended.
         * 
         * @return Bundle with this view's state
         */
        public Bundle saveState(Bundle map) {
            synchronized (mSurfaceHolder) {
                if (map != null) {

                }
            }
            return map;
        }

        /**
         * Sets the current difficulty.
         * 
         * @param difficulty
         */
        public void setDifficulty(int difficulty) {
            synchronized (mSurfaceHolder) {
            }
        }

        
        /**
         * Used to signal the thread whether it should be running or not.
         * Passing true allows the thread to run; passing false will shut it
         * down if it's already running. Calling start() after this was most
         * recently called with false will result in an immediate shutdown.
         * 
         * @param b true to run, false to shut down
         */
        public void setRunning(boolean b) {
            mRun = b;
        }

        /**
         * Sets the game mode. That is, whether we are running, paused, in the
         * failure state, in the victory state, etc.
         * 
         * @see #setState(int, CharSequence)
         * @param mode one of the STATE_* constants
         */
        public void setState(int mode) {
            synchronized (mSurfaceHolder) {
                //setState(mode, null);
            }
        }

        /* Callback invoked when the surface dimensions change. */
        public void setSurfaceSize(int width, int height) {
            // synchronized to make sure these all change atomically
        	
            synchronized (mSurfaceHolder) {
            	Log.d("GameView", "setSurfaceSize()");
            	
            	mCanvasWidth = width;
                mCanvasHeight = height;

                // don't forget to resize the background image
                mGameHandler.resizeBackGroundImg(width, height);
            	mGameHandler.createGameObjects();
            }
        }

        /**
         * Resumes from a pause.
         */
        public void unpause() {
            // Move the real time clock up to now
            synchronized (mSurfaceHolder) {
            	
            }
            //setState(STATE_RUNNING);
        }

        /**
         * Handles a key-down event.
         * 
         * @param keyCode the key that was pressed
         * @param msg the original event object
         * @return true
         */
        boolean doKeyDown(int keyCode, KeyEvent msg) {
            synchronized (mSurfaceHolder) {
            	/*
                boolean okStart = false;
                if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true;
                if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true;
                if (keyCode == KeyEvent.KEYCODE_S) okStart = true;

                boolean center = (keyCode == KeyEvent.KEYCODE_DPAD_UP);

                if (okStart
                        && (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) {
                    // ready-to-start -> start
                    doStart();
                    return true;
                } else if (mMode == STATE_PAUSE && okStart) {
                    // paused -> running
                    unpause();
                    return true;
                } else if (mMode == STATE_RUNNING) {
                    // center/space -> fire
                    if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                            || keyCode == KeyEvent.KEYCODE_SPACE) {
                        setFiring(true);
                        return true;
                        // left/q -> left
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
                            || keyCode == KeyEvent.KEYCODE_Q) {
                        mRotating = -1;
                        return true;
                        // right/w -> right
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
                            || keyCode == KeyEvent.KEYCODE_W) {
                        mRotating = 1;
                        return true;
                        // up -> pause
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                        pause();
                        return true;
                    }
                }
				*/
                return false;
            }
        }

        /**
         * Handles a key-up event.
         * 
         * @param keyCode the key that was pressed
         * @param msg the original event object
         * @return true if the key was handled and consumed, or else false
         */
        boolean doKeyUp(int keyCode, KeyEvent msg) {
            boolean handled = false;

            synchronized (mSurfaceHolder) {
            	/*
                if (mMode == STATE_RUNNING) {
                    if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                            || keyCode == KeyEvent.KEYCODE_SPACE) {
                        setFiring(false);
                        handled = true;
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
                            || keyCode == KeyEvent.KEYCODE_Q
                            || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
                            || keyCode == KeyEvent.KEYCODE_W) {
                        mRotating = 0;
                        handled = true;
                    }
                }
                */
            }

            return handled;
        }
        
        
        boolean doTouchEvent( MotionEvent event) {
        	boolean bhandled = false;
        	
        	synchronized ( mSurfaceHolder ) {
        		bhandled = mGameHandler.doTouchEvent(event);
        	}
        	
        	return bhandled;
        }
        
        /*
        void onMotionSensorChange(MyWorld.WorldDirection dir) {
        	synchronized ( mSurfaceHolder ) {
        		mGameHandler.changeDirection(dir);
        	}
        }
        */
        
        void onMotionSensorChange(float x, float y) {
        	synchronized ( mSurfaceHolder ) {
        		mGameHandler.changeDirection(x, y);
        	}
        }
    }

    /** Handle to the application context, used to e.g. fetch Drawables. */
    private Context mContext;

    /** Pointer to the text view to display "Paused.." etc. */
    private TextView mStatusText;

    /** The thread that actually draws the animation */
    private GameThread thread;

    public GameView(Context context, AttributeSet attrs) {
	   super(context, attrs);
	    
	   try {	
	        // register our interest in hearing about changes to our surface
	        SurfaceHolder holder = getHolder();
	        holder.addCallback(this);
	
	        // create thread only; it's started in surfaceCreated()
	        mContext = context;
	        thread = new GameThread(holder, context, new Handler() {
	            @Override
	            public void handleMessage(Message m) {
	                mStatusText.setVisibility(m.getData().getInt("viz"));
	                mStatusText.setText(m.getData().getString("text"));
	            }
	        });
	        thread.setRunning(false);
	        
	        // Get Sensor
	        mSensorManager = (SensorManager)context.getSystemService(context.SENSOR_SERVICE);
	        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
	        
	        // Get Display
	        mWindowManager = (WindowManager)context.getSystemService(context.WINDOW_SERVICE);
	        mDisplay = mWindowManager.getDefaultDisplay();
	        
	        setFocusable(true); // make sure we get key events
    	} catch( Exception e) {
			Log.e("Exception", "Exception! " + e.getMessage());
			Log.e("Exception", "---Call Stack---\n");
			for( StackTraceElement ste : e.getStackTrace()) {
				Log.e("Exception", " " + ste.getClassName() + "." + ste.getMethodName()
						               + "(" + ste.getLineNumber() + ")\n");
			}
			throw new RuntimeException(e);
		} finally {
			//Do nothing here
		}
    }

	
    /**
     * Fetches the animation thread corresponding to this LunarView.
     * 
     * @return the animation thread
     */
    public GameThread getThread() {
        return thread;
    }

    /**
     * Standard override to get key-press events.
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent msg) {
        return thread.doKeyDown(keyCode, msg);
    }

    /**
     * Standard override for key-up. We actually care about these, so we can
     * turn off the engine or stop rotating.
     */
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent msg) {
        return thread.doKeyUp(keyCode, msg);
    }

    public boolean onTouchEvent( MotionEvent event ) {
		return thread.doTouchEvent(event);
    }
    
    /**
     * Standard window-focus override. Notice focus lost so we can pause on
     * focus lost. e.g. user switches to take a call.
     */
    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        if (!hasWindowFocus) thread.pause();
    }

    /**
     * Installs a pointer to the text view used for messages.
     */
    public void setTextView(TextView textView) {
        mStatusText = textView;
    }

    /* Callback invoked when the surface dimensions change. */
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
    	try {
    		thread.setSurfaceSize(width, height);
    	} catch ( Exception e) {
			Log.e("Exception", "Exception! " + e.getMessage());
			Log.e("Exception", "---Call Stack---\n");
			for( StackTraceElement ste : e.getStackTrace()) {
				Log.e("Exception", " " + ste.getClassName() + "." + ste.getMethodName()
						               + "(" + ste.getLineNumber() + ")\n");
			}
    	}
    }

    /*
     * Callback invoked when the Surface has been created and is ready to be
     * used.
     */
    public void surfaceCreated(SurfaceHolder holder) {
        // start the thread here so that we don't busy-wait in run()
        // waiting for the surface to be created
    	try {
    		Log.d("GameView", "sufaceCreated()");
            if (thread.getState() == Thread.State.TERMINATED) {
    	        thread = new GameThread(holder, mContext, thread.mHandler, thread.mGameHandler);
                thread.setRunning(true);
                thread.start();
                //thread.doResume();
            } else {
                thread.setRunning(true);
                thread.start();
            }
    	} catch( Exception e ) {
			Log.e("Exception", "Exception! " + e.getMessage());
			Log.e("Exception", "---Call Stack---\n");
			for( StackTraceElement ste : e.getStackTrace()) {
				Log.e("Exception", " " + ste.getClassName() + "." + ste.getMethodName()
						               + "(" + ste.getLineNumber() + ")\n");
			}
    	}
    }

    /*
     * Callback invoked when the Surface has been destroyed and must no longer
     * be touched. WARNING: after this method returns, the Surface/Canvas must
     * never be touched again!
     */
    public void surfaceDestroyed(SurfaceHolder holder) {
        // we have to tell thread to shut down & wait for it to finish, or else
        // it might touch the Surface after we return and explode
        boolean retry = true;
        
        Log.d("GameView", "surfaceDestroyed()");
        thread.setRunning(false);
        while (retry) {
            try {
                thread.join();
                retry = false;
            } catch (Exception e) {
    			Log.e("Exception", "Exception! " + e.getMessage());
    			Log.e("Exception", "---Call Stack---\n");
    			for( StackTraceElement ste : e.getStackTrace()) {
    				Log.e("Exception", " " + ste.getClassName() + "." + ste.getMethodName()
    						               + "(" + ste.getLineNumber() + ")\n");
    			}
            }
        }
    }
    
	public void update(){
		try {
			Log.d("GameView", "update()");
			postInvalidate();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			Log.e("Exception", "Exception! " + e.getMessage());
			Log.e("Exception", "---Call Stack---\n");
			for( StackTraceElement ste : e.getStackTrace()) {
				Log.e("Exception", " " + ste.getClassName() + "." + ste.getMethodName()
						               + "(" + ste.getLineNumber() + ")\n");
			}
		}
	}
	
   
    private final SensorManager mSensorManager;
    private final Sensor mAccelerometer;
    private WindowManager mWindowManager;
    private Display mDisplay;

    protected void StartGame() {
        /*
         * It is not necessary to get accelerometer events at a very high
         * rate, by using a slower rate (SENSOR_DELAY_UI), we get an
         * automatic low-pass filter, which "extracts" the gravity component
         * of the acceleration. As an added benefit, we use less power and
         * CPU resources.
         */
        mSensorManager.registerListener(mListener, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
    }

    protected void PauseGame() {
    	thread.pause(); 
        mSensorManager.unregisterListener(mListener);
    }
    

    private final SensorEventListener mListener = new SensorEventListener() {

        private final float[] mScale = new float[] { 2, 2.5f, 0.5f };   // accel
        private float[] mPrev = new float[3];
        private long mLastGestureTime;

        public void onSensorChanged(SensorEvent event) {
        	long now = android.os.SystemClock.uptimeMillis();
        	
        	//Interval: 50ms
            if (now - mLastGestureTime > 50) {
                boolean show = false;
                float[] diff = new float[3];

                for (int i = 0; i < 3; i++) {
                    diff[i] = Math.round(mScale[i] * (event.values[i] - mPrev[i]) * 0.45f);
                    if (Math.abs(diff[i]) > 0) {
                        show = true;
                    }
                }

                if (show) {
                    // only shows if we think the delta is big enough, in an attempt
                    // to detect "serious" moves left/right or up/down
                    Log.v("AccelerometerSensor", "sensorChanged " + event.sensor.getName() +
                            " (" + event.values[0] + ", " + event.values[1] + ", " +
                            event.values[2] + ")" + " diff(" + diff[0] +
                            " " + diff[1] + " " + diff[2] + ")");
                }                
                //Switch x & y since we are using landscape for screen direction
                float x = event.values[1];
                boolean gestX = Math.abs(diff[1]) > 0.1;
                float y = event.values[0];
                boolean gestY = Math.abs(diff[0]) > 0.1;
                
                if (gestX || gestY) {
                	thread.onMotionSensorChange(x, y );
                	
                	mLastGestureTime = now;
                    for (int i = 0; i < 3; i++) {
                        mPrev[i] = event.values[i];
                    }
                	Log.v("AccelerometerSensor", "sensorChanged x: " + x + " , y: " + y );
                }
                /*
                if ((gestX || gestY) && !(gestX && gestY)) {
                    if (gestX) {
                        if (x < 0) {
                        	thread.onMotionSensorChange(MyWorld.WorldDirection.LEFT);
                            Log.e("test", "<<<<<<<< LEFT <<<<<<<<<<<<");
                        } else {
                        	thread.onMotionSensorChange(MyWorld.WorldDirection.RIGHT);
                            Log.e("test", ">>>>>>>>> RITE >>>>>>>>>>>");
                        }
                    } else {
                        if (y < -2) {
                        	thread.onMotionSensorChange(MyWorld.WorldDirection.UP);
                            Log.e("test", "<<<<<<<< UP <<<<<<<<<<<<");
                        } else {
                        	thread.onMotionSensorChange(MyWorld.WorldDirection.DOWN);
                            Log.e("test", ">>>>>>>>> DOWN >>>>>>>>>>>");
                        }
                    }
                    mLastGestureTime = now;
                }
                */
            }
        }
        
/*
        @Override
        public void onSensorChanged(SensorEvent event) {
            if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
                return;

//             * record the accelerometer data, the event's timestamp as well as
//             * the current time. The latter is needed so we can calculate the
//             * "present" time during rendering. In this application, we need to
//             * take into account how the screen is rotated with respect to the
//             * sensors (which always return data in a coordinate space aligned
//             * to with the screen in its native orientation).
             

            switch (mDisplay.getRotation()) {
                case Surface.ROTATION_0:
                	thread.onMotionSensorChange(MyWorld.WorldDirection.RIGHT);
                    Log.e("test", ">>>>>>>>> RITE >>>>>>>>>>>");
                    //mSensorX = event.values[0];
                    //mSensorY = event.values[1];
                    break;
                case Surface.ROTATION_90:
                	thread.onMotionSensorChange(MyWorld.WorldDirection.DOWN);
                    Log.e("test", ">>>>>>>>> DOWN >>>>>>>>>>>");
                    //mSensorX = -event.values[1];
                    //mSensorY = event.values[0];
                    break;
                case Surface.ROTATION_180:
                	thread.onMotionSensorChange(MyWorld.WorldDirection.UP);
                    Log.e("test", "<<<<<<<< UP <<<<<<<<<<<<");
                    //mSensorX = -event.values[0];
                    //mSensorY = -event.values[1];
                    break;
                case Surface.ROTATION_270:
                	thread.onMotionSensorChange(MyWorld.WorldDirection.LEFT);
                    Log.e("test", "<<<<<<<< LEFT <<<<<<<<<<<<");
                    //mSensorX = event.values[1];
                    //mSensorY = -event.values[0];
                    break;
            }

            //mSensorTimeStamp = event.timestamp;
            //mCpuTimeStamp = System.nanoTime();
        }
*/

        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }
    };

}
